﻿/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A
 * copy of the license can be found at http://go.microsoft.com/fwlink/?LinkID=131993. If
 * you cannot locate the Microsoft Public License, please send an email to
 * mixon@microsoft.com. By using this source code in any fashion, you are agreeing to
 * be bound by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;

namespace Microsoft.Controls
{

    [TemplateVisualState(Name = VirtualizedListBoxItem.StateNormal, GroupName = VirtualizedListBoxItem.GroupCommon)]
    [TemplateVisualState(Name = VirtualizedListBoxItem.StateMouseOver, GroupName = VirtualizedListBoxItem.GroupCommon)]
    [TemplateVisualState(Name = VirtualizedListBoxItem.StateUnselected, GroupName = VirtualizedListBoxItem.GroupSelection)]
    [TemplateVisualState(Name = VirtualizedListBoxItem.StateSelected, GroupName = VirtualizedListBoxItem.GroupSelection)]
    [TemplateVisualState(Name = VirtualizedListBoxItem.StateSelectedUnfocused, GroupName = VirtualizedListBoxItem.GroupSelection)]
    [TemplateVisualState(Name = VirtualizedListBoxItem.StateUnfocused, GroupName = VirtualizedListBoxItem.GroupFocus)]
    [TemplateVisualState(Name = VirtualizedListBoxItem.StateFocused, GroupName = VirtualizedListBoxItem.GroupFocus)]
    public class VirtualizedListBoxItem : ContentControl
    {
        /// <summary>
        /// Gets or sets a value that indicates whether a ListBoxItem is selected.
        /// </summary>
        public bool IsSelected
        {
            get { return (bool)GetValue(IsSelectedProperty); }
            set { SetValue(IsSelectedProperty, value); }
        }

        /// <summary>
        /// Identifies the IsSelected dependency property.
        /// </summary>
        public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(
            "IsSelected", typeof(bool), typeof(VirtualizedListBoxItem),
            new PropertyMetadata(OnIsSelectedChanged));

        public event RoutedEventHandler Selected;
        public event RoutedEventHandler Unselected;

        /// <summary>
        /// Gets a value that indicates whether the mouse pointer is located over this element.
        /// </summary>
        internal bool IsMouseOver { get; private set; }

        /// <summary>
        /// Gets a value that indicates whether this element has focus.
        /// </summary>
        internal bool IsFocused { get; private set; }

        /// <summary>
        /// Identifies the ItemsControl item represented by this object (null when this object IS the item)
        /// </summary>
        internal object Item { get; set; }

        /// <summary>
        /// Identifies the parent ListBox.
        /// </summary>
        internal VirtualizedListBox ParentListBox { get; set; }

        /// <summary>
        /// Identifies the Normal state.
        /// </summary>
        private const string StateNormal = "Normal";

        /// <summary>
        /// Identifies the MosueOver state.
        /// </summary>
        private const string StateMouseOver = "MouseOver";

        /// <summary>
        /// Name of the Common state group.
        /// </summary>
        private const string GroupCommon = "CommonStates";

        /// <summary>
        /// Identifies the Unselected state.
        /// </summary>
        private const string StateUnselected = "Unselected";

        /// <summary>
        /// Identifies the Selected state.
        /// </summary>
        private const string StateSelected = "Selected";

        /// <summary>
        /// Identifies the SelectedUnfocused state.
        /// </summary>
        private const string StateSelectedUnfocused = "SelectedUnfocused";

        /// <summary>
        /// Name of the Selection state group.
        /// </summary>
        private const string GroupSelection = "SelectionStates";

        /// <summary>
        /// Focused state of the Button.
        /// </summary>
        internal const string StateFocused = "Focused";

        /// <summary>
        /// Unfocused state of the Button.
        /// </summary>
        internal const string StateUnfocused = "Unfocused";

        /// <summary>
        /// Focus state group of the Button.
        /// </summary>
        internal const string GroupFocus = "FocusStates";

        /// <summary>
        /// Initializes a new instance of the ListBoxItem class.
        /// </summary>
        public VirtualizedListBoxItem()
        {

            VisualStateManager.SetCustomVisualStateManager(this, new MyCustomVisualStateManager());
            this.IsTabStop = true; 

            MouseLeftButtonDown += delegate(object sender, MouseButtonEventArgs e)
            {
                OnMouseLeftButtonDown(e);
            };
            MouseEnter += delegate(object sender, MouseEventArgs e)
            {
                OnMouseEnter(e);
            };
            MouseLeave += delegate(object sender, MouseEventArgs e)
            {
                OnMouseLeave(e);
            };
            GotFocus += delegate(object sender, RoutedEventArgs e)
            {
                OnGotFocus(e);
            };
            LostFocus += delegate(object sender, RoutedEventArgs e)
            {
                OnLostFocus(e);
            };
            Loaded += delegate
            {
                ChangeVisualState(false);
            };

            DefaultStyleKey = typeof(VirtualizedListBoxItem);
        }

        /// <summary>
        /// Invoked whenever application code or internal processes call
        /// ApplyTemplate.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            ChangeVisualState(false);
        }

        /// <summary>
        /// Called when the user presses the left mouse button over the ListBoxItem.
        /// </summary>
        /// <param name="e">The event data.</param>
        internal virtual void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            if (!e.Handled)
            {
                e.Handled = true;
                if (this.Focus()) // If we click on an item, it should take focus.
                {
                    if (null != ParentListBox)
                    {
                        ParentListBox.NotifyListItemClicked(this);
                    }
                }
            }
        }

        /// <summary>
        /// Called when the mouse pointer enters the bounds of this element.
        /// </summary>
        /// <param name="e">The event data.</param>
        internal virtual void OnMouseEnter(MouseEventArgs e)
        {
            IsMouseOver = true;
            ChangeVisualState();
        }

        /// <summary>
        /// Called when the mouse pointer leaves the bounds of this element.
        /// </summary>
        /// <param name="e">The event data.</param>
        internal virtual void OnMouseLeave(MouseEventArgs e)
        {
            IsMouseOver = false;
            ChangeVisualState();
        }

        /// <summary>
        /// Called when the control got focus.
        /// </summary>
        /// <param name="e">The event data.</param>
        internal virtual void OnGotFocus(RoutedEventArgs e)
        {
            IsFocused = true;
            ChangeVisualState();

            if (null != ParentListBox)
            {
                ParentListBox.NotifyListItemGotFocus(this);
            }
        }

        /// <summary>
        /// Called when the control lost focus.
        /// </summary>
        /// <param name="e">The event data.</param>
        internal virtual void OnLostFocus(RoutedEventArgs e)
        {
            IsFocused = false;
            ChangeVisualState();

            if (null != ParentListBox)
            {
                ParentListBox.NotifyListItemLostFocus(this);
            }
        }

        /// <summary>
        /// Implements the IsSelectedProperty PropertyChangedCallback.
        /// </summary>
        /// <param name="d">The DependencyObject for which the property changed.</param>
        /// <param name="e">Provides data for DependencyPropertyChangedEventArgs.</param>
        private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var listBoxItem = d as VirtualizedListBoxItem;
            var newValue = (bool)e.NewValue;
            if (newValue)
            {
                listBoxItem.OnSelected(new RoutedEventArgs());
            }
            else
            {
                listBoxItem.OnUnselected(new RoutedEventArgs());
            }
            listBoxItem.ChangeVisualState();
        }

        /// <summary>
        /// Change to the correct visual state for the ListBoxItem.
        /// </summary>
        internal void ChangeVisualState()
        {
            ChangeVisualState(true);
        }

        /// <summary>
        /// Change to the correct visual state for the ListBoxItem.
        /// </summary>
        /// <param name="useTransitions">
        /// true to use transitions when updating the visual state, false to
        /// snap directly to the new visual state.
        /// </param>
        internal void ChangeVisualState(bool useTransitions)
        {
            // Change to the correct state in the Interaction group
            if (IsMouseOver)
            {
                this.GoToState(useTransitions, StateMouseOver, StateNormal);
            }
            else
            {
                this.GoToState(useTransitions, StateNormal);
            }

            // Change to the correct state in the Selection group
            if (IsSelected)
            {
                if (ParentListBox == null || !(VirtualizedListBox.GetIsSelectionActive(ParentListBox)))
                {
                    this.GoToState(useTransitions, StateSelectedUnfocused, StateSelected, StateUnselected);
                }
                else
                {
                    this.GoToState(useTransitions, StateSelected, StateUnselected);
                }
            }
            else
            {
                this.GoToState(useTransitions, StateUnselected);
            }

            if (IsFocused)
            {
                this.GoToState(useTransitions, StateFocused, StateUnfocused);
            }
            else
            {
                this.GoToState(useTransitions, StateUnfocused);
            }
        }

        private void GoToState(bool useTransitions, params string[] stateNames)
        {
            if (stateNames == null)
            {
                return;
            }

            foreach (string name in stateNames)
            {
                if (VisualStateManager.GoToState(this, name, useTransitions))
                {
                    break;
                }
            }
        }

        private void OnSelected(RoutedEventArgs e)
        {
            RoutedEventHandler eventHandler = this.Selected;
            if (eventHandler != null)
            {
                eventHandler(this, e);
            }
        }

        private void OnUnselected(RoutedEventArgs e)
        {
            RoutedEventHandler eventHandler = this.Unselected;
            if (eventHandler != null)
            {
                eventHandler(this, e);
            }
        }

    }

    public class MyCustomVisualStateManager : VisualStateManager
    {
        private class ExtraInfoForVisualStateGroup
        {
            public VisualState CurrentState { get; set; }
            public Storyboard OldStoryboard { get; set; }
        }

        private Dictionary<VisualStateGroup, ExtraInfoForVisualStateGroup> extraInfoDictionary = new Dictionary<VisualStateGroup, ExtraInfoForVisualStateGroup>();

        protected override bool GoToStateCore(Control control, FrameworkElement templateRoot, string stateName, VisualStateGroup group, VisualState state, bool useTransitions)
        {
            // If no group was found containing a state with the specified name, there's nothing we can do here.
            if (group == null)
            {
                return false;
            }

            // Look up (or create) the extra info associated with the specified group.
            ExtraInfoForVisualStateGroup extraInfo;
            if (!this.extraInfoDictionary.TryGetValue(group, out extraInfo))
            {
                extraInfo = new ExtraInfoForVisualStateGroup();
                this.extraInfoDictionary.Add(group, extraInfo);
            }

            // If we were already in the right state, no change is needed so we just return true.
            VisualState lastState = extraInfo.CurrentState;
            if (lastState == state)
            {
                return true;
            }

            // Start new state Storyboard and stop any previously running Storyboards
            // the state.Storyboard is second in the list so that it "wins" and will 
            // override any animation in the dynamicTransition.
            this.StopOldThenStartNew(templateRoot, state.Storyboard, extraInfo.OldStoryboard);
            extraInfo.OldStoryboard = state.Storyboard;
            extraInfo.CurrentState = state;

            return true;
        }

        private void StopOldThenStartNew(FrameworkElement element, Storyboard newStoryboard, Storyboard oldStoryboard)
        {
            if (oldStoryboard != null)
            {
                oldStoryboard.Stop();
                RemoveLiveStoryboard(element, oldStoryboard);
            }
            if (newStoryboard != null)
            {
                EnsureLiveStoryboard(element, newStoryboard);
                newStoryboard.Begin();
            }
        }

        private static void EnsureLiveStoryboard(FrameworkElement element, Storyboard storyboard)
        {
            if ((storyboard != null) && !element.Resources.Contains(GetResourceKey(storyboard)))
            {
                element.Resources.Add(GetResourceKey(storyboard), storyboard);
            }
        }

        private static void RemoveLiveStoryboard(FrameworkElement element, Storyboard storyboard)
        {
            element.Resources.Remove(GetResourceKey(storyboard));
        }

        private static string GetResourceKey(Storyboard storyboard)
        {
            return storyboard.GetHashCode().ToString();
        }
    }
}
